﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using Flower.Client;
using Flower.Directory.Host.Default;
using Flower.Directory.Management;
using Flower.Logging;
using Flower.Services;
using Flower.Services.Data;
using Flower.Testing;
using NUnit.Framework;
using Flower.Workflow;
using Flower.Processing.Interruptions;
using SortOrder = Flower.Workflow.SortOrder;
using Flower.Directory.Util;
using Flower.Directory.Host;
using Spring.Context.Support;
using Spring.Context;
using System.Threading.Tasks;

#pragma warning disable 0649

namespace Flower.Processing.Tests
{
    [TestFixture]
    public class ProcessBuilderTests
    {
        private static readonly Log Log = LogManager.CreateLog(typeof(ProcessBuilderTests).Name);
        private IApplicationContext _appContext;

        private const int POS1 = 1;
        private const int POS2 = 1 << 1;
        private const int POS3 = 1 << 2;
        private const int POS4 = 1 << 3;
        private const int POS5 = 1 << 4;
        private const int POS6 = 1 << 5;
        private const int POS7 = 1 << 6;
        private const int POS8 = 1 << 7;
        private const int POS9 = 1 << 8;
        private const int POS10 = 1 << 9;

        #region Setup

        public IDirectory CreateDirectory()
        {
            var dir = (IDirectory)_appContext.GetObject("directory");

            return dir;
        }

        private class Service
        {
            public int ReturnOne() { return 1; }
        }

        private IFlowerClient _flowerClient;
        
        [TestFixtureSetUp]
        public void Setup()
        {
            _appContext = new XmlApplicationContext("Flower.Directory.Host.config");

            _flowerClient = new FlowerClient(CreateDirectory());
        }

        [TestFixtureTearDown]
        public void TearDown()
        {
            _flowerClient.Dispose();
        }

        #endregion

        #region If

        [DataContract]
        private class IfProcess : Workflow.Process, IProcessDefinition
        {
            public bool Cond;

            public int Pos1;
            public int Pos2;
            public int Pos3;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { Pos1 = POS1; })

                    .If(_ => Cond)
                        .Exec(_ => { Pos2 = POS2; })
                    .End()

                    .Exec(_ => { Pos3 = POS3; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void If()
        {
            var prc = new Processor("Test", _flowerClient);

            var proc = new IfProcess { Cond = false };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3) == (POS1 | POS3));

            proc = new IfProcess { Cond = true };
            prc.Init("PID", proc);
                
            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3) == (POS1 | POS2 | POS3));
        }

        [DataContract]
        private class IfElseProcess : Workflow.Process, IProcessDefinition
        {
            public bool Cond;

            public int Pos1;
            public int Pos2;
            public int Pos3;
            public int Pos4;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { Pos1 = POS1; })

                    .If(_ => Cond)
                        .Exec(_ => { Pos2 = POS2; })
                    .Else()
                        .Exec(_ => { Pos3 = POS3; })
                    .End()

                    .Exec(_ => { Pos4 = POS4; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void IfElse()
        {
            var prc = new Processor("Test", _flowerClient);

            var proc = new IfElseProcess { Cond = false };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3 | proc.Pos4) == (POS1 | POS3 | POS4));

            proc = new IfElseProcess { Cond = true };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3 | proc.Pos4) == (POS1 | POS2 | POS4));
        }

        [DataContract]
        private class IfElseIfElseProcess : Workflow.Process, IProcessDefinition
        {
            public bool Cond1;
            public bool Cond2;

            public int Pos1;
            public int Pos2;
            public int Pos3;
            public int Pos4;
            public int Pos5;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { Pos1 = POS1; })

                    .If(_ => Cond1)
                        .Exec(_ => { Pos2 = POS2; })
                    .ElseIf(_ => Cond2)
                        .Exec(_ => { Pos3 = POS3; })
                    .Else()
                        .Exec(_ => { Pos4 = POS4; })
                    .End()

                    .Exec(_ => { Pos5 = POS5; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void IfElseIfElse()
        {
            var prc = new Processor("Test", _flowerClient);

            var proc = new IfElseIfElseProcess { Cond1 = false, Cond2 = false };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3 | proc.Pos4 | proc.Pos5) == (POS1 | POS4 | POS5));

            proc = new IfElseIfElseProcess { Cond1 = false, Cond2 = true };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3 | proc.Pos4 | proc.Pos5) == (POS1 | POS3 | POS5));

            proc = new IfElseIfElseProcess { Cond1 = true, Cond2 = false };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3 | proc.Pos4 | proc.Pos5) == (POS1 | POS2 | POS5));

            proc = new IfElseIfElseProcess { Cond1 = true, Cond2 = true };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3 | proc.Pos4 | proc.Pos5) == (POS1 | POS2 | POS5));
        }
        #endregion

        #region While

        [DataContract]
        private class WhileProcess : Workflow.Process, IProcessDefinition
        {
            public int Iterations;

            public int Cnt;
            public int Pos1;
            public int Pos2;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { Pos1 = POS1; })

                    .While(_ => Cnt < Iterations)
                        .Exec(_ => { Cnt++; })
                    .End()

                    .Exec(_ => { Pos2 = POS2; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void While()
        {
            var prc = new Processor("Test", _flowerClient);

            var proc = new WhileProcess { Iterations = 3 };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue(proc.Cnt == proc.Iterations);
            Assert.IsTrue((proc.Pos1 | proc.Pos2) == (POS1 | POS2));
        }

        [DataContract]
        private class WhileContinueBreakProcess : Workflow.Process, IProcessDefinition
        {
            public int Iterations;

            public int Cnt;
            public int Pos1;
            public int Pos2;
            public int Pos3;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { Pos1 = POS1; })

                    .While(_ => true)
                        .If(_ => Cnt == Iterations)
                            .Break()
                        .Else()
                            .Exec(_ => { Cnt++; })
                            .Continue()
                            .Exec(_ => { Pos2 = POS2; })
                        .End()
                    .End()

                    .Exec(_ => { Pos3 = POS3; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void WhileContinueBreak()
        {
            var prc = new Processor("Test", _flowerClient);

            var proc = new WhileContinueBreakProcess { Iterations = 3 };
            prc.Init("PID", proc);
                
            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue(proc.Cnt == proc.Iterations);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3) == (POS1 | POS3));
        }

        #endregion

        #region For

        [DataContract]
        private class ForProcess : Workflow.Process, IProcessDefinition
        {
            public int Iterations;

            public int Pos1;
            public int Pos2;
            public int Pos3;

            public int I;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { Pos1 = POS1; })

                    .For(_ => { I = 0; }, _ => I < Iterations, _ => { I++; })
                        .Exec(_ => { Pos2 = POS2; })
                    .End()

                    .Exec(_ => { Pos3 = POS3; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void For()
        {
            var prc = new Processor("Test", _flowerClient);

            var proc = new ForProcess { Iterations = 3 };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue(proc.I == proc.Iterations);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3) == (POS1 | POS2 | POS3));
        }

        [DataContract]
        private class ForBreakContinueProcess : Workflow.Process, IProcessDefinition
        {
            public int Iterations;

            public int Cnt;
            public int Pos1;
            public int Pos2;
            public int Pos3;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { Pos1 = POS1; })

                    .For(_ => { }, _ => true, _ => { })
                        .If(_ => Cnt == Iterations)
                            .Break()
                        .Else()
                            .Exec(_ => { Cnt++; })
                            .Continue()
                            .Exec(_ => { Pos2 = POS2; })
                        .End()
                    .End()

                    .Exec(_ => { Pos3 = POS3; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void ForContinueBreak()
        {
            var prc = new Processor("Test", _flowerClient);

            var proc = new ForBreakContinueProcess { Iterations = 3 };
            prc.Init("PID", proc);

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue(proc.Cnt == proc.Iterations);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3) == (POS1 | POS3));
        }

        #endregion

        #region Try Catch

        [DataContract]
        private class TryCatchProcess : Workflow.Process, IProcessDefinition
        {
            public int passed;

            private readonly Exception _exception;

            public TryCatchProcess(Exception exception)
            {
                _exception = exception;
            }

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { passed = 0; })
                    .Exec(_ => { passed |= POS1; })

                    .Try()
                        .Exec(_ => { passed |= POS2; })

                        .If(_ => _exception != null)
                            .Throw(_ => _exception)
                        .End()
                    .Catch<InvalidOperationException>((ctx, ex) => {})
                        .Exec(_ => { passed |= POS3; })
                    .Catch<ArgumentException>((ctx, ex) => { })
                        .Exec(_ => { passed |= POS4; })
                    .Catch<Exception>((ctx, ex) => { })
                        .Exec(_ => { passed |= POS5; })
                    .Finally()
                        .Exec(_ => { passed |= POS6; })
                    .End()

                    .Exec(_ => { passed |= POS7; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void TryCatch()
        {
            var dir = CreateDirectory();

            string pid;

            new DirectoryBuilder(dir)
                .Root("/Processors")
                    .Processor("Proc1", (string)null)
                        .Folder("Running")
                            .Process
                            (
                                typeof(TryCatchProcess).AssemblyQualifiedName,
                                out pid
                            ).End()
                        .End()
                        .Folder("Pending").End()
                        .Folder("Suspended").End()
                    .End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var prc = new Processor("Test", cli);
                var proc = new TryCatchProcess(null);
                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Finish);
                Assert.AreEqual
                (
                    POS1 | 
                    POS2 | 
                    POS6 | 
                    POS7,
                    proc.passed
                );

                prc = new Processor("Test", cli);
                proc = new TryCatchProcess(new InvalidOperationException());
                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Finish);
                Assert.AreEqual
                (
                    POS1 |
                    POS2 |
                    POS3 |
                    POS6 |
                    POS7,
                    proc.passed
                );

                prc = new Processor("Test", cli);
                proc = new TryCatchProcess(new ArgumentException());
                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Finish);
                Assert.AreEqual
                (
                    POS1 |
                    POS2 |
                    POS4 |
                    POS6 |
                    POS7,
                    proc.passed
                );

                prc = new Processor("Test", cli);
                proc = new TryCatchProcess(new IndexOutOfRangeException());
                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Finish);
                Assert.AreEqual
                (
                    POS1 |
                    POS2 |
                    POS5 |
                    POS6 |
                    POS7,
                    proc.passed
                );
            }
        }

        #endregion

        #region Invoke

        [DataContract]
        private class InvokeProcess : Workflow.Process, IProcessDefinition
        {
            public int retvalDynamic;
            public int retvalInjected;

            [Service("Test/Service")]
            private Service service;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Invoke<Service>(_ => "/Services/Test/Service", (_, svc) => { retvalDynamic = svc.ReturnOne(); })
                    .Invoke(service, (_, svc) => { retvalInjected = svc.ReturnOne(); });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        private static readonly string TEST_SERVICE_CONTAINER =
            @"<objects xmlns=""http://www.springframework.net"">" +
            @"  <object id=""Service"" type=""" + typeof(Service).AssemblyQualifiedName + @""" />" +
            @"</objects>";

        [Test]
        public void Invoke()
        {
            var dir = CreateDirectory();

            string pid;

            new DirectoryBuilder(dir)
                .Root("/Services")
                    .ServiceContainer("Test", TEST_SERVICE_CONTAINER).End()
                .End()
                .Root("/Processors")
                    .Processor("Proc1", (string)null)
                        .Folder("Running")
                            .Process
                            (
                                typeof(InvokeProcess).AssemblyQualifiedName,
                                out pid
                            ).End()
                        .End()
                        .Folder("Pending").End()
                        .Folder("Suspended").End()
                    .End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var proc = new InvokeProcess();
                var prc = new Processor("Proc1", cli);

                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Finish);
                Assert.IsTrue(proc.retvalDynamic == 1);
                Assert.IsTrue(proc.retvalInjected == 1);
                Assert.IsTrue(dir.CountChildren(pid, DirectoryEntryTypes.State) == 2);
            }
        }

        #endregion

        #region Lock/Unlock

        [DataContract]
        private class LockUnlockProcess : Workflow.Process, IProcessDefinition
        {
            public const string RES1 = "/Sets/Shared/Resource1/Root";
            public const string RES2 = "/Sets/Shared/Resource2/Root";

            public static readonly string[] Resources = new string[] { RES1, RES2 };

            public bool FirstLockFailed;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Lock
                    (
                        _ => new[] { RES1 },
                        ctx => ctx.Pid,
                        _ => false,
                        (ctx, failures) => { FirstLockFailed = true; }
                    )
                    .Lock
                    (
                        _ => new[] { RES2 },
                        ctx => ctx.Pid,
                        _ => true
                    )
                    .Unlock(_ => Resources, ctx => ctx.Pid);
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void LockUnlock()
        {
            var dir = CreateDirectory();

            string pid;

            new DirectoryBuilder(dir)
                .Root("/Sets/Shared")
                    .Set("Resource1", typeof(int).FullName, 0).End()
                    .Set("Resource2", typeof(int).FullName, 0)
                        .Folder("Root")
                            .Message("X", BlobFormat.TextXml, 1).End()
                        .End()
                    .End()
                .End()
                .Root("/Processors")
                    .Processor("Proc1", (string)null)
                        .Folder("Running")
                            .Process
                            (
                                typeof(LockUnlockProcess).FullName, 
                                out pid
                            ).End()
                        .End()
                        .Folder("Waiting").End()
                        .Folder("Pending").End()
                    .End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var proc = new LockUnlockProcess();
                var prc = new Processor("Proc1", cli);

                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Waiting);
                Assert.IsFalse(dir.Exists("/Processors/Proc1/Running/" + pid, DateTime.MinValue));
                Assert.IsTrue(dir.Exists("/Processors/Proc1/Waiting/" + pid, DateTime.MinValue));

                prc.Client.Unlock(new[] { LockUnlockProcess.RES2 }, "X");
                Assert.IsFalse(dir.Exists("/Processors/Proc1/Waiting/" + pid, DateTime.MinValue));
                Assert.IsTrue(dir.Exists("/Processors/Proc1/Pending/" + pid, DateTime.MinValue));

                Assert.IsTrue(prc.Execute() is Finish);
            }
        }

        #endregion

        #region Enqueue/Dequeue

        [DataContract]
        private class EnqueueDequeueProcess : Workflow.Process, IProcessDefinition
        {
            public const string SRC = "/Sets/Shared/Src";
            public const string DEST = "/Sets/Shared/Dest";

            private int i;
            private int message;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .For(_ => { i = 0; }, _ => i < 10, _ => { ++i; })
                        .Dequeue<int>(_ => SRC, (ctx, msg) => { message = msg; })
                        .Enqueue(_ => DEST, _ => message, _ => MessageFormat.BinXml)
                    .End();
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void EnqueueDequeue()
        {
            var dir = CreateDirectory();
            
            string pid;

            new DirectoryBuilder(dir)
                .Root("/Sets/Shared")
                    .Set("Src", typeof(int).FullName, 0).End()
                    .Set("Dest", typeof(int).FullName, 5).End()
                .End()
                .Root("/Processors")
                    .Processor("Proc1", (string)null)
                        .Folder("Running")
                            .Process
                            (
                                typeof(LockUnlockProcess).FullName,
                                out pid
                            ).End()
                        .End()
                        .Folder("Waiting").End()
                        .Folder("Pending").End()
                    .End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var proc = new EnqueueDequeueProcess();
                var prc = new Processor("Proc1", cli);

                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Waiting);
                Assert.IsFalse(dir.Exists("/Processors/Proc1/Running/" + pid, DateTime.MinValue));
                Assert.IsTrue(dir.Exists("/Processors/Proc1/Waiting/" + pid, DateTime.MinValue));

                for (int i = 0; i < 10; i++)
                {
                    Assert.IsTrue(cli.PutMessage(EnqueueDequeueProcess.SRC, i.ToString(), i, BlobFormat.BinXml));
                }

                dir.Move(pid, "/Processors/Proc1/Running");

                Assert.IsTrue(prc.Execute() is Waiting);
                Assert.IsFalse(dir.Exists("/Processors/Proc1/Running/" + pid, DateTime.MinValue));
                Assert.IsTrue(dir.Exists("/Processors/Proc1/Waiting/" + pid, DateTime.MinValue));
                Assert.AreEqual(5, dir.CountChildren(EnqueueDequeueProcess.DEST, DirectoryEntryTypes.Message));

                var messages = dir.GetChildren(EnqueueDequeueProcess.DEST, DirectoryEntryTypes.Message, SortProperty.Default, Services.Data.SortOrder.Asc, 0, long.MaxValue);
                foreach(var msg in messages)
                {
                    cli.RemoveMessage(msg.Id);
                }

                Assert.AreEqual(0, dir.CountChildren(EnqueueDequeueProcess.DEST, DirectoryEntryTypes.Message));

                dir.Move(pid, "/Processors/Proc1/Running");

                Assert.IsTrue(prc.Execute() is Finish);
                Assert.AreEqual(5, dir.CountChildren(EnqueueDequeueProcess.DEST, DirectoryEntryTypes.Message));
            }
        }

        #endregion

        #region ForEach

        [DataContract]
        private class ForEachProcess : Workflow.Process, IProcessDefinition
        {
            public const string SET = "/Sets/Shared/Messages";

            private int i;
            public int sum;
            private int item;
            private string itemName;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .For(_ => { i = 0; }, _ => i < 8, _ => { i++; })
                        .Enqueue(_ => SET, _ => i.ToString(), _ => 1 << i, _ => MessageFormat.BinXml)
                    .End()

                    .Exec(_ => { sum = 0; })
                    
                    .ForEach
                    (
                        _ => SET,
                        _ => MessageProperty.Default,
                        _ => SortOrder.Asc,
                        _ => 100,
                        _ => false,
                        (string _, int itm) => { item = itm; }
                    )
                        .Exec(_ => { sum = sum | item; })
                    .End()

                    .Breakpoint("First sum")

                    .ForEach
                    (
                        _ => SET,
                        _ => MessageProperty.Default,
                        _ => SortOrder.Asc,
                        _ => 100,
                        _ => false,
                        (string itmName, int itm) => { item = itm; itemName = itmName; }
                    )
                        .UpdateCurrent(_ => itemName, _ => item << 8, _ => MessageFormat.BinXml)
                    .End()

                    .ForEach
                    (
                        _ => SET,
                        _ => MessageProperty.Default,
                        _ => SortOrder.Asc,
                        _ => 100,
                        _ => false,
                        (string _, int itm) => { item = itm; }
                    )
                        .Exec(_ => { sum = sum | item; })
                    .End()

                    .Breakpoint("Second sum")

                    .ForEach
                    (
                        _ => SET,
                        _ => MessageProperty.Default,
                        _ => SortOrder.Asc,
                        _ => 100,
                        _ => false,
                        (string _, int itm) => { item = itm; }
                    )
                        .If(_ => item < 0x1000)
                            .RemoveCurrent()
                        .End()
                    .End()

                    .Exec(_ => { sum = 0; })

                    .ForEach
                    (
                        _ => SET,
                        _ => MessageProperty.Default,
                        _ => SortOrder.Asc,
                        _ => 100,
                        _ => false,
                        (string _, int itm) => { item = itm; }
                    )
                        .Exec(_ => { sum = sum | item; })
                    .End()

                    .Breakpoint("Third sum");
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void ForEach()
        {
            var dir = CreateDirectory();

            string pid;

            new DirectoryBuilder(dir)
                .Root("/Sets/Shared")
                    .Set("Messages", typeof(int).FullName, 0).End()
                .End()
                .Root("/Processors")
                    .Processor("Proc1", (string)null)
                        .Folder("Running")
                            .Process
                            (
                                typeof(LockUnlockProcess).FullName,
                                out pid
                            ).End()
                        .Folder("Waiting").End()
                        .Folder("Pending").End()
                    .End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var proc = new ForEachProcess();
                var prc = new Processor("Proc1", cli, null, new Processor.Settings { EmitBreakpoints = true });

                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Breakpoint);
                Assert.AreEqual(0xFF, proc.sum);

                Assert.IsTrue(prc.Execute() is Breakpoint);
                Assert.AreEqual(0xFFFF, proc.sum);

                Assert.IsTrue(prc.Execute() is Breakpoint);
                Assert.AreEqual(0xF000, proc.sum);
            }
        }

        [DataContract]
        private class ForEachWaitingProcess : Workflow.Process, IProcessDefinition
        {
            public const string SET = "/Sets/Shared/Messages";

            public int sum;
            private int item;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { sum = 0; })

                    .ForEach
                    (
                        _ => SET,
                        _ => MessageProperty.Default,
                        _ => SortOrder.Asc,
                        _ => 100,
                        _ => true,
                        (string _, int itm) => { item = itm; }
                    )
                        .Exec(_ => { sum = sum | item; })
                    .End();
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void ForEachWaiting()
        {
            var dir = CreateDirectory();

            string pid;

            new DirectoryBuilder(dir)
                .Root("/Sets/Shared")
                    .Set("Messages", typeof(int).FullName, 0).End()
                .End()
                .RegisterProcessor("Proc1")
                .Root("/Processors/Proc1/Running")
                    .Process
                    (
                        typeof(LockUnlockProcess).FullName,
                        out pid
                    ).End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var proc = new ForEachWaitingProcess();
                var prc = new Processor("Proc1", cli);

                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Waiting);

                for (int i = 0; i < 8; i++)
                {
                    prc.Client.PutMessage(ForEachWaitingProcess.SET, i.ToString(), 1 << i, BlobFormat.BinXml);
                }

                dir.Move(pid, "/Processors/Proc1/Running");

                Assert.IsTrue(prc.Execute() is Waiting);
                Assert.AreEqual(0xFF, proc.sum);

                proc.sum = 0;

                for (int i = 8; i < 16; i++)
                {
                    prc.Client.PutMessage(ForEachWaitingProcess.SET, i.ToString(), 1 << i, BlobFormat.BinXml);
                }

                dir.Move(pid, "/Processors/Proc1/Running");

                Assert.IsTrue(prc.Execute() is Waiting);
                Assert.AreEqual(0xFF00, proc.sum);
            }
        }

        #endregion

        #region WaitUntil

        [DataContract]
        private class WaitUntilProcess : Workflow.Process, IProcessDefinition
        {
            public DateTime start;
            public bool finished;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .WaitUntil(_ => start + TimeSpan.FromSeconds(1), _ => { finished = true; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void WaitUntil()
        {
            var dir = CreateDirectory();

            string pid;

            new DirectoryBuilder(dir)
                .Root("/Processors")
                    .Processor("Proc1", (string)null)
                        .Folder("Running")
                            .Process
                            (
                                typeof(LockUnlockProcess).FullName,
                                out pid
                            ).End()
                        .Folder("Suspended").End()
                    .End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var proc = new WaitUntilProcess { start = DateTime.Now };
                var prc = new Processor("Proc1", cli);

                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Suspention);
                Assert.AreEqual(1, cli.Directory.CountChildren("/Processors/Proc1/Suspended", DirectoryEntryTypes.Link));

                Thread.Sleep(2000);

                Assert.IsTrue(prc.Execute() is Finish);
            }            
        }

        #endregion

        #region StartProcess/WaitProcess

        [DataContract]
        public class EmptyWorkflow : Workflow.Process, IWorkflow<int, int>
        {
            public void DefineLocalSets(ISetsBuilder bld)
            {
            }

            public int Initialize(IInitializationContext context, int arg)
            {
                return arg + 1;
            }

            public void DefineProcess(IProcessBuilder bld)
            {
            }
        }

        [DataContract]
        public class StartProcessWaitProcessProcess : Workflow.Process, IProcessDefinition
        {
            private const string WORKFLOW = "/Workflows/Empty";

            public string pid;
            public int startResult;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .StartProcess<int, int>
                    (
                        _ => WORKFLOW, 
                        _ => 1, 
                        (_, p, result) => 
                        {
                            pid = p;
                            startResult = result;
                        }
                    )

                    .JoinProcess(_ => pid, null);
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void StartProcessWaitProcess()
        {
            var dir = CreateDirectory();

            string pid;

            new DirectoryBuilder(dir)
                .Root("/Workflows")
                    .Workflow("Empty", typeof(EmptyWorkflow).AssemblyQualifiedName, null).End()
                .End()
                .RegisterProcessor("Proc1")
                .Root("/Processors/Proc1/Running")
                    .Process
                    (
                        typeof(LockUnlockProcess).FullName,
                        out pid
                    ).End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var proc = new StartProcessWaitProcessProcess();
                var prc = new Processor("Proc1", cli);

                prc.Init(pid, proc);

                Assert.IsTrue(prc.Execute() is Waiting);
                Assert.AreEqual(2, proc.startResult);

                cli
                    .CreateScript()
                    .SetText("__changeProcessStatus($in.pid, 'Finished');")
                    .AddParameter("pid", proc.pid)
                    .Execute();

                Assert.IsTrue(prc.Execute() is Finish);
            }
        }

        #endregion

        #region WaitAny

        [DataContract]
        private class WaitAnyProcess : Workflow.Process, IProcessDefinition
        {
            public const string GET_SET = "/Sets/Shared/Get";
            public const string PUT_SET = "/Sets/Shared/Put";
            public const string ENQUEUE_SET = "/Sets/Shared/Enqueue";
            public const string LOCK_SET = "/Sets/Shared/Lock";
            private const string WORKFLOW = "/Workflows/Empty";

            public DateTime waitUntil;
            public int count;
            public string pid;
            private int iterationsCount; 
            
            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ =>
                    {
                        count = 0;
                        iterationsCount = 0;
                        waitUntil = DateTime.Now + TimeSpan.FromDays(1);
                    })

                    .StartProcess<int, int>
                    (
                        _ => WORKFLOW,
                        _ => 1,
                        (ctx, p, result) => { pid = p; }
                    )

                    .While(_ => count != (1 | 2 | 4 | 8 | 16 | 32) && iterationsCount <= 6)
                        .WaitAny()
                            .Enqueue(_ => PUT_SET, _ => "Msg", _ => 1, _ => MessageFormat.BinXml, _ => { count = count | 1; })
                            .Enqueue(_ => ENQUEUE_SET, _ => 1, _ => MessageFormat.BinXml, _ => { count = count | 2; })
                            .Dequeue<int>(_ => GET_SET, (_, msg) => { count = count | 4; })
                            .Lock
                            (
                                _ => new[] { LOCK_SET },
                                _ => ((count & 8) == 0) ? "Proc1" : "Proc2", 
                                _ => true,
                                (_, failures) => { if (failures.Length == 0) count = count | 8; }
                            )
                            .JoinProcess(_ => pid, _ => { count = count | 16; })
                            .WaitUntil(_ => waitUntil, _ => { count = count | 32; })
                        .End()

                        .If(_ => (count & 16) != 0)
                            .StartProcess<int, int>
                            (
                                _ => WORKFLOW,
                                _ => 1,
                                (_, p, result) => { pid = p; }
                            )
                        .End()

                        .Exec(_ => { ++iterationsCount; })
                    .End();
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void WaitAny()
        {
            var dir = CreateDirectory();

            string pid;

            new DirectoryBuilder(dir)
                .Root("/Workflows")
                    .Workflow("Empty", typeof(EmptyWorkflow).AssemblyQualifiedName, null).End()
                .End()
                .Root("/Sets/Shared")
                    .Set("Get", typeof(int).FullName, 0).End()
                    .Set("Put", typeof(int).FullName, 1)
                        .Message("Msg", BlobFormat.BinXml, 1).End()
                    .End()
                    .Set("Enqueue", typeof(int).FullName, 1)
                        .Message("Msg", BlobFormat.BinXml, 1).End()
                    .End()
                    .Set("Lock", typeof(int).FullName, 1)
                        .Message("Owner", BlobFormat.BinXml, 1).End()
                    .End()
                .End()
                .RegisterProcessor("Proc1")
                .Root("/Processors/Proc1/Running")
                    .Process
                    (
                        typeof(LockUnlockProcess).FullName,
                        out pid
                    ).End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var proc = new WaitAnyProcess();
                var prc = new Processor("Proc1", cli);

                prc.Init(pid, proc);

                var suspention = prc.Execute() as Suspention;

                Assert.IsTrue(suspention != null && !suspention.Stop);
                Assert.IsTrue(prc.Execute() is Waiting);
                Assert.IsTrue(dir.CountChildren(WaitAnyProcess.GET_SET + "/GetWaiters", DirectoryEntryTypes.Link) > 0);

                cli.PutMessage(WaitAnyProcess.GET_SET, "Msg", 1, BlobFormat.BinXml);
                dir.Move(pid, "/Processors/Proc1/Running");
                foreach (var suspentionLink in dir.GetChildren("/Processors/Proc1/Suspended", DirectoryEntryTypes.Link, SortProperty.Default, Services.Data.SortOrder.Asc, 0, long.MaxValue))
                {
                    dir.Remove(suspentionLink.Id);
                }

                suspention = prc.Execute() as Suspention;
                Assert.IsTrue(suspention != null && !suspention.Stop);
                Assert.IsTrue(prc.Execute() is Waiting);

                cli.RemoveMessage("/Sets/Shared/Put/Msg");
                dir.Move(pid, "/Processors/Proc1/Running");
                foreach (var suspentionLink in dir.GetChildren("/Processors/Proc1/Suspended", DirectoryEntryTypes.Link, SortProperty.Default, Services.Data.SortOrder.Asc, 0, long.MaxValue))
                {
                    dir.Remove(suspentionLink.Id);
                }

                suspention = prc.Execute() as Suspention;
                Assert.IsTrue(suspention != null && !suspention.Stop);
                Assert.IsTrue(prc.Execute() is Waiting);

                cli.RemoveMessage("/Sets/Shared/Enqueue/Msg");
                dir.Move(pid, "/Processors/Proc1/Running");
                foreach (var suspentionLink in dir.GetChildren("/Processors/Proc1/Suspended", DirectoryEntryTypes.Link, SortProperty.Default, Services.Data.SortOrder.Asc, 0, long.MaxValue))
                {
                    dir.Remove(suspentionLink.Id);
                }

                suspention = prc.Execute() as Suspention;
                Assert.IsTrue(suspention != null && !suspention.Stop);
                Assert.IsTrue(prc.Execute() is Waiting);

                cli.RemoveMessage("/Sets/Shared/Lock/Owner");
                dir.Move(pid, "/Processors/Proc1/Running");
                foreach (var suspentionLink in dir.GetChildren("/Processors/Proc1/Suspended", DirectoryEntryTypes.Link, SortProperty.Default, Services.Data.SortOrder.Asc, 0, long.MaxValue))
                {
                    dir.Remove(suspentionLink.Id);
                }

                suspention = prc.Execute() as Suspention;
                Assert.IsTrue(suspention != null && !suspention.Stop);
                Assert.IsTrue(prc.Execute() is Waiting);

                cli
                    .CreateScript()
                    .SetText("__changeProcessStatus($in.pid, 'Finished');")
                    .AddParameter("pid", proc.pid)
                    .Execute();
                dir.Move(pid, "/Processors/Proc1/Running");
                foreach (var suspentionLink in dir.GetChildren("/Processors/Proc1/Suspended", DirectoryEntryTypes.Link, SortProperty.Default, Services.Data.SortOrder.Asc, 0, long.MaxValue))
                {
                    dir.Remove(suspentionLink.Id);
                }

                suspention = prc.Execute() as Suspention;
                Assert.IsTrue(suspention != null && !suspention.Stop);
                Assert.IsTrue(prc.Execute() is Waiting);

                dir.Move(pid, "/Processors/Proc1/Running");
                foreach (var suspentionLink in dir.GetChildren("/Processors/Proc1/Suspended", DirectoryEntryTypes.Link, SortProperty.Default, Services.Data.SortOrder.Asc, 0, long.MaxValue))
                {
                    dir.Remove(suspentionLink.Id);
                }

                proc.waitUntil = DateTime.Now;

                Assert.IsTrue(prc.Execute() is Finish);
                Assert.AreEqual(1 | 2 | 4 | 8 | 16 | 32, proc.count);
            }
        }

        #endregion

        #region Breakpoint

        [DataContract]
        private class BreakpointProcess : Workflow.Process, IProcessDefinition
        {
            public int Pos1;
            public int Pos2;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { Pos1 = POS1; })

                    .Breakpoint("break")

                    .Exec(_ => { Pos2 = POS2; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void Breakpoint()
        {
            var proc = new BreakpointProcess();
            var prc = new Processor("Test", _flowerClient, null, new Processor.Settings { EmitBreakpoints = true });

            prc.Init("PID", proc);

            var bp = prc.Execute() as Breakpoint;

            Assert.IsTrue(bp != null);
            Assert.IsTrue(bp.Name == "break");
            Assert.IsTrue(prc.Execute() is Finish);
        }

        #endregion

        #region Save/Load process

        [DataContract]
        private class SaveLoadProcessProcess : Workflow.Process, IWorkflow<object, object>
        {
            [DataMember]
            public int counter; 

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }

            public object Initialize(IInitializationContext context, object arg)
            {
                return null;
            }

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Breakpoint("Break1")
                    .Exec(_ => { counter = 0; })
                    .ExecAndSave(_ => { counter++; })
                    .Breakpoint("Break2")
                    .ExecAndSave(_ => { counter++; });
            }
        }

        [Test]
        public void SaveLoadProcess()
        {
            var dir = CreateDirectory();

            string pid;

            new DirectoryBuilder(dir)
                .Root("/Processors")
                    .Processor("Proc", (string)null)
                        .Folder("Running")
                            .Process
                            (
                                typeof(SaveLoadProcessProcess).AssemblyQualifiedName,
                                out pid
                            )
                            .End()
                        .End()
                    .End()
                .End();

            using (var cli = new FlowerClient(dir))
            {
                var prc = new SaveLoadProcessProcess();
                var proc = new Processor("Proc", cli, null, new Processor.Settings { EmitBreakpoints = true });

                proc.Init(pid, prc);

                Assert.IsTrue(proc.Execute() is Breakpoint);
                proc.SaveState(false);

                proc = new Processor("Proc", cli, null, new Processor.Settings { EmitBreakpoints = true });
                proc.LoadProcess(pid);
                prc = (SaveLoadProcessProcess)proc.State;
                
                Assert.IsTrue(proc.Execute() is Breakpoint);
                Assert.AreEqual(1, prc.counter);
                proc.SaveState(false);

                proc = new Processor("Proc", cli, null, new Processor.Settings { EmitBreakpoints = true });

                proc.LoadProcess(pid);
                prc = (SaveLoadProcessProcess)proc.State;

                Assert.AreEqual(1, prc.counter);

                Assert.IsTrue(proc.Execute() is Finish);
                Assert.AreEqual(2, prc.counter);
            }
        }

        #endregion

        #region ExecAsync

        [DataContract]
        private class ExecAsyncProcess : Workflow.Process, IProcessDefinition
        {
            public int Pos1;
            public int Pos2;
            public int Pos3;

            public void DefineProcess(IProcessBuilder bld)
            {
                bld
                    .Exec(_ => { Pos1 = POS1; })
                    .ExecAsync
                    (
                        async _ => 
                        {
                            await Task.Factory.StartNew(() => Pos2 = POS2);
                        }
                    )
                    .Exec(_ => { Pos3 = POS3; });
            }

            public void DefineLocalSets(ISetsBuilder bld)
            {
            }
        }

        [Test]
        public void ExecAsync()
        {
            var prc = new Processor("Test", _flowerClient);

            var proc = new ExecAsyncProcess();
            prc.Init("PID", proc);

            Async asyncInterruption;

            Assert.IsTrue((asyncInterruption = prc.Execute() as Async) != null);

            asyncInterruption.Task.Wait();

            Assert.IsTrue(prc.Execute() is Finish);
            Assert.IsTrue((proc.Pos1 | proc.Pos2 | proc.Pos3) == (POS1 | POS2 | POS3));
        }

        #endregion
    }
}
